﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using mal.WP7.GoogleAnalytics.Business.Repository;

namespace mal.WP7.GoogleAnalytics.Business.GoogleApi
{
    public class GoogleAnalyticsService : GoogleAnalyticsServiceBase
    {
        #region Fields
        private string auth;
        private string sid;
        private string lsid;
        private Dictionary<string, string> headers;
        #endregion

        #region Construction / Destruction
        public GoogleAnalyticsService(string login, string password)
            : base(login, password)
        {

        }
        #endregion

        #region Properties

        #endregion

        #region Methods
        public override void LoginAsync(object useState)
        {
            IsLoading = true;
            Uri uri = new Uri("https://www.google.com/accounts/ClientLogin", UriKind.Absolute);
            Dictionary<string, string> datas = new Dictionary<string, string>();

            mal.WP7.Framework.Net.HttpWebRequest request = new mal.WP7.Framework.Net.HttpWebRequest();
            request.DownloadStringCompleted += new mal.WP7.Framework.Net.DownloadStringCompletedEventHandler(this.OnLoginCompleted);

            datas.Add("accountType", "HOSTED_OR_GOOGLEGOOGLE");
            datas.Add("Email", this.Login);
            datas.Add("Passwd", this.Password);
            datas.Add("service", "analytics");
            datas.Add("source", "mal-wp7-1.0");

            request.PostRequestAsync(uri, datas, useState);
        }

        public override void GetFeedAsync(FeedRequest request, object userState)
        {
            Uri uri = new Uri("https://www.google.com/analytics/feeds/data", UriKind.Absolute);

            mal.WP7.Framework.Net.HttpWebRequest httpRequest = new mal.WP7.Framework.Net.HttpWebRequest();
            httpRequest.DownloadStringCompleted += new mal.WP7.Framework.Net.DownloadStringCompletedEventHandler(this.OnLoadDataFeed);
            Dictionary<string, string> datas = new Dictionary<string, string>();

            datas.Add("ids", request.WebSiteId);
            datas.Add("start-date", request.StartDate.ToString("yyyy-MM-dd"));
            datas.Add("end-date", request.EndDate.ToString("yyyy-MM-dd"));
            if (request.Metrics != null)
            {
                string[] metricsArray = new string[request.Metrics.Length];
                for (int i = 0; i < request.Metrics.Length; i++)
                {
                    metricsArray[i] = string.Format("ga:{0}", request.Metrics[i].ToString().ToLower());
                }

                datas.Add("metrics", string.Join(",", metricsArray));
            }

            List<Dimension> dimensionsList = new List<Dimension>();
            dimensionsList.Add(request.Dimension);

            switch (request.Dimension)
            {
                case Dimension.Day:
                    dimensionsList.Add(Dimension.Month);
                    dimensionsList.Add(Dimension.Year);
                    break;
                case Dimension.Week:
                    dimensionsList.Add(Dimension.Year);
                    break;
                case Dimension.Month:
                    dimensionsList.Add(Dimension.Year);
                    break;
            }

            string[] dimensionsArray = new string[dimensionsList.Count];

            for (int i = 0; i < dimensionsList.Count; i++)
            {
                dimensionsArray[i] = string.Format("ga:{0}", dimensionsList[i].ToString().ToLower());
            }

            datas.Add("dimensions", string.Join(",", dimensionsArray));

            datas.Add("sort", string.Join(",", dimensionsArray.Reverse().ToArray()));

            GetFeedUserState args = new GetFeedUserState();
            args.Request = request;
            args.UserState = userState;

            httpRequest.GetRequestAsync(uri, datas, this.headers, args);
        }

        private void LoadAccountFeedAsync(object useState)
        {
            Uri uri = new Uri("https://www.google.com/analytics/feeds/accounts/default", UriKind.Absolute);
            Dictionary<string, string> datas = new Dictionary<string, string>();
            mal.WP7.Framework.Net.HttpWebRequest request = new mal.WP7.Framework.Net.HttpWebRequest();
            request.DownloadStringCompleted += new mal.WP7.Framework.Net.DownloadStringCompletedEventHandler(this.OnLoadAccountFeed);

            request.GetRequestAsync(uri, datas, this.headers, useState);
        }

        private string GetMetricValue(List<XNode> metricNodes, string metric)
        {
            foreach (XElement node in metricNodes)
            {
                XAttribute nameAttribute = node.Attributes().Where(a => a.Name == "name").First();
                if (nameAttribute.Value == metric)
                {
                    XAttribute valueAttribute = node.Attributes().Where(a => a.Name == "value").First();
                    return valueAttribute.Value;
                }
            }

            return null;
        }

        private double GetMetricValue(XElement metricNode)
        {
            XAttribute valueAttribute = metricNode.Attributes().Where(a => a.Name == "value").First();
            return double.Parse(valueAttribute.Value);
        }

        private Metric GetMetric(XElement metricNode)
        {
            XAttribute valueAttribute = metricNode.Attributes().Where(a => a.Name == "name").First();
            string metricName = valueAttribute.Value.Replace("ga:", string.Empty);

            Metric result = Metric.Unknown;

            try
            {
                result = (Metric)Enum.Parse(typeof(Metric), metricName, true);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            return result;
        }

        private DateTime? GetDate(XElement entryNode)
        {
            int? year = null;
            int? month = null;
            int? week = null;
            int day = 1;
            DateTime? result = null;

            List<XNode> dimensionNodes = entryNode.Nodes().Where(node => ((XElement)node).Name.LocalName == "dimension").ToList();

            foreach (XElement dimensionNode in dimensionNodes)
            {
                XAttribute nameAttribute = dimensionNode.Attributes().Where(a => a.Name == "name").First();
                XAttribute valueAttribute = dimensionNode.Attributes().Where(a => a.Name == "value").First();

                string dimensionName = nameAttribute.Value.Replace("ga:", string.Empty);

                Dimension dimension = (Dimension)Enum.Parse(typeof(Dimension), dimensionName, true);
                switch (dimension)
                {
                    case Dimension.Day:
                        day = int.Parse(valueAttribute.Value);
                        break;

                    case Dimension.Week:
                        week = int.Parse(valueAttribute.Value);
                        break;

                    case Dimension.Month:
                        month = int.Parse(valueAttribute.Value);
                        break;

                    case Dimension.Year:
                        year = int.Parse(valueAttribute.Value);
                        break;
                }
            }

            if (year.HasValue && month.HasValue)
            {
                result = new DateTime(year.Value, month.Value, day);
            }
            else if (year.HasValue && week.HasValue)
            {
                result = new DateTime(year.Value, 1, 1);
                GregorianCalendar calendar = new GregorianCalendar();
                result = calendar.AddWeeks(result.Value, week.Value);
            }

            return result;
        }
        #endregion

        #region Events
        private void OnLoginCompleted(object sender, mal.WP7.Framework.Net.DownloadStringCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                this.RaiseLoginCompletedEvent(new LoginCompletedEventArgs(e.Error, e.UserState));
            }
            else
            {
                string[] lines = e.Result.Split("\n".ToCharArray());
                for (int i = 0; i < lines.Length; i++)
                {
                    string line = lines[i];
                    if (line.StartsWith("SID="))
                    {
                        this.sid = line.Substring(4, line.Length - 4);
                    }

                    if (line.StartsWith("LSID="))
                    {
                        this.lsid = line.Substring(5, line.Length - 5);
                    }

                    if (line.StartsWith("Auth="))
                    {
                        this.auth = line.Substring(5, line.Length - 5);
                    }
                }

                this.headers = new Dictionary<string, string>();
                this.headers.Add("Authorization", "GoogleLogin Auth=" + this.auth);
                this.LoadAccountFeedAsync(e.UserState);
            }
        }

        private void OnLoadAccountFeed(object sender, mal.WP7.Framework.Net.DownloadStringCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                this.RaiseLoginCompletedEvent(new LoginCompletedEventArgs(e.Error, e.UserState));
            }
            else
            {
                List<Website> websites = new List<Website>();
                string xml = e.Result;

                XDocument doc = XDocument.Parse(xml);
                List<XNode> websiteNodes = doc.Root.Nodes().Where(node => ((XElement)node).Name.LocalName == "entry").ToList();

                foreach (XElement websiteNode in websiteNodes)
                {
                    XElement tableId = (XElement)websiteNode.Nodes().Where(node => ((XElement)node).Name.LocalName == "tableId").FirstOrDefault();
                    XElement name = (XElement)websiteNode.Nodes().Where(node => ((XElement)node).Name.LocalName == "title").FirstOrDefault();

                    Website website = new Website();
                    website.Id = tableId.Value;
                    website.Name = name.Value;

                    websites.Add(website);
                }

                this.RaiseLoginCompletedEvent(new LoginCompletedEventArgs(websites, e.UserState));
            }
        }

        private void OnLoadDataFeed(object sender, mal.WP7.Framework.Net.DownloadStringCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                GetFeedCompletedEventArgs args = new GetFeedCompletedEventArgs(e.Error, ((GetFeedUserState)e.UserState).UserState);
            }
            else
            {
                GetFeedUserState userState = (GetFeedUserState)e.UserState;
                List<Statistic> values = new List<Statistic>();
                XDocument doc = XDocument.Parse(e.Result);
                List<XNode> entryNodes = doc.Root.Nodes().Where(node => ((XElement)node).Name.LocalName == "entry").ToList();
                DateTime? date;

                foreach (XElement entryNode in entryNodes)
                {
                    date = this.GetDate(entryNode);

                    List<XNode> metricNodes = entryNode.Nodes().Where(node => ((XElement)node).Name.LocalName == "metric").ToList();

                    foreach (XElement metricNode in metricNodes)
                    {
                        Statistic value = new Statistic();
                        value.Dimension = userState.Request.Dimension;
                        value.Metric = this.GetMetric(metricNode);
                        value.Value = this.GetMetricValue(metricNode);

                        if (date.HasValue)
                        {
                            value.StartDate = date.Value;

                            switch (userState.Request.Dimension)
                            {
                                case Dimension.Day:
                                    date = date.Value.AddDays(1);
                                    break;

                                case Dimension.Week:
                                    if (date.Value.DayOfWeek != DayOfWeek.Sunday)
                                    {
                                        while (date.Value.DayOfWeek != DayOfWeek.Sunday)
                                        {
                                            date = date.Value.AddDays(1);
                                        }
                                    }
                                    else
                                    {
                                        date = date.Value.AddDays(7);
                                    }

                                    break;

                                case Dimension.Month:
                                    if (date.Value.Month < 12)
                                    {
                                        date = new DateTime(date.Value.Year, date.Value.Month + 1, 1);
                                    }
                                    else
                                    {
                                        date = new DateTime(date.Value.Year + 1, 1, 1);
                                    }

                                    break;
                            }

                            value.EndDate = date.Value;
                        }

                        values.Add(value);
                    }
                }

                GetFeedCompletedEventArgs args = new GetFeedCompletedEventArgs(userState.UserState);
                args.Statistics = values;
                args.WebsiteId = userState.Request.WebSiteId;
                this.RaiseGetFeedCompletedEvent(args);
            }
        }
        #endregion

        #region enum, struct, class
        private class GetFeedUserState
        {
            public object UserState { get; set; }

            public FeedRequest Request { get; set; }
        }
        #endregion
    }
}
